'''
下位机主程序。
使用前请修改IP为上位机IP，端口为上位机端口，并确保上位机程序已启动。
创建日期：2024-01-25
更新日期：2024-05-10
'''
import sys
import threading
import numpy as np
import time
import RPi.GPIO as GPIO
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer, QThread, pyqtSignal
from bgwhite2 import Ui_mainWindow
from ds18b20 import DS18B20
from pin_dic import pin_dic
from dht11 import DHT11
from raspi_socket import TCPClient  # 导入 TCP 通信模块中的 send_data 函数
from max30102 import MAX30102

# 25 samples per second (in algorithm.h)
SAMPLE_FREQ = 25
# taking moving average of 4 samples when calculating HR
# in algorithm.h, "DONOT CHANGE" comment is attached
MA_SIZE = 4
# sampling frequency * 4 (in algorithm.h)
BUFFER_SIZE = 100
#max30102信号class
class SensorSignals(QtCore.QObject):
    hr_spo2_updated = pyqtSignal(bool, float, float)
#max30102数据采集与计算线程类class
class Max30102(QThread):
    def __init__(self):
        super().__init__()
        self.sensor = MAX30102()
        self.signals = SensorSignals()
        self.buffer_size = 100
        self.flag_finger = False
        self.ir_data = []
        self.red_data = []

        self.bpms = []
        self.spo2s = []
        self.hr_mean = None
        self.spo2_mean = None

    def calc_hr_spo2(self,ir_data,red_data):
        
        # 计算均值
        ir_mean = int(np.mean(ir_data))
        
        # 减均值，前面加- ，为了计算波谷
        x = -1 * (np.array(ir_data) - ir_mean)
        
        # 进行均值滤波
        for i in range(x.shape[0] - MA_SIZE):
            x[i] = np.sum(x[i:i+MA_SIZE]) / MA_SIZE
            
        # 计算阈值，将阈值设置在30-60之间
        n_th = int(np.mean(x))
        n_th = 30 if n_th < 30 else n_th  # min allowed
        n_th = 60 if n_th > 60 else n_th  # max allowed
        
        # 获取信号中波谷的个数，以及波谷的位置
        ir_valley_locs, n_peaks = self.find_peaks(x, BUFFER_SIZE, n_th, min_dist=4, max_num=15)
        
        # 计算各个波谷的间距之和
        peak_interval_sum = 0
        
        if n_peaks >= 2:
            for i in range(1, n_peaks):
                peak_interval_sum += (ir_valley_locs[i] - ir_valley_locs[i-1])
            peak_interval_sum = int(peak_interval_sum / (n_peaks - 1))
            
            hr = int(SAMPLE_FREQ * 60 / peak_interval_sum)
            hr_valid = True
        else:
            hr = -999  # unable to calculate because # of peaks are too small
            hr_valid = False
            spo2 = -999  # do not use SPO2 since valley loc is out of range
            spo2_valid = False
            
            return hr_valid, hr,spo2_valid,spo2
            
        #--------------下面进行spo2的计算---------------#
        
        i_ratio_count = 0
        ratio = []
        
        for k in range(n_peaks-1):
            red_dc_max = -16777216
            ir_dc_max = -16777216
            
            if ir_valley_locs[k+1] - ir_valley_locs[k] > 3:
                # 找到两个波谷之间的最大值
                for i in range(ir_valley_locs[k], ir_valley_locs[k+1]):
                    if ir_data[i] > ir_dc_max:
                        ir_dc_max = ir_data[i]
                        ir_dc_max_index = i
                    if red_data[i] > red_dc_max:
                        red_dc_max = red_data[i]
                        red_dc_max_index = i
                    
                # 计算 AC 值    
                red_ac = int((red_data[ir_valley_locs[k+1]] - red_data[ir_valley_locs[k]]) * (red_dc_max_index - ir_valley_locs[k]))
                red_ac = red_data[ir_valley_locs[k]] + int(red_ac / (ir_valley_locs[k+1] - ir_valley_locs[k]))
                red_ac = red_data[red_dc_max_index] - red_ac  # subtract linear DC components from raw

                ir_ac = int((ir_data[ir_valley_locs[k+1]] - ir_data[ir_valley_locs[k]]) * (ir_dc_max_index - ir_valley_locs[k]))
                ir_ac = ir_data[ir_valley_locs[k]] + int(ir_ac / (ir_valley_locs[k+1] - ir_valley_locs[k]))
                ir_ac = ir_data[ir_dc_max_index] - ir_ac  # subtract linear DC components from raw

                nume = red_ac * ir_dc_max
                denom = ir_ac * red_dc_max
                if (denom > 0 and i_ratio_count < 5) and nume != 0:
                    
                    # 这里对ratio计算的结果*100，进行精度扩大
                    ratio.append(int(((nume * 100) & 0xffffffff) / denom))
                    i_ratio_count += 1
        
        
        # 选取中值作为输出
        ratio = sorted(ratio)  # sort to ascending order
        mid_index = int(i_ratio_count / 2)

        ratio_ave = 0
        if mid_index > 1:
            ratio_ave = int((ratio[mid_index-1] + ratio[mid_index])/2)
        else:
            if len(ratio) != 0:
                ratio_ave = ratio[mid_index]
        
        # 对ratio的合理性进行判断
        if ratio_ave > 2 and ratio_ave < 184:
            # -45.060 * ratioAverage * ratioAverage / 10000 + 30.354 * ratioAverage / 100 + 94.845
            spo2 = -45.060 * (ratio_ave**2) / 10000.0 + 30.054 * ratio_ave / 100.0 + 94.845
            spo2_valid = True
        else:
            spo2 = -999
            spo2_valid = False
        
        return  hr_valid, hr,spo2_valid,spo2


    # 寻找峰值，峰值的高度>min_height, 峰值最多 max_num 个
    #  峰值之间的间隔要<min_dist
    # 返回峰值（波谷）的位置以及峰值的个数
    def find_peaks(self,x, size, min_height, min_dist, max_num):
        
        # 寻找峰值
        ir_valley_locs, n_peaks = self.find_peaks_above_min_height(x, size, min_height, max_num)
        
        # 消除较大的峰值
        ir_valley_locs, n_peaks = self.remove_close_peaks(n_peaks, ir_valley_locs, x, min_dist)

        n_peaks = min([n_peaks, max_num])

        return ir_valley_locs, n_peaks

    # 寻找大于min_height的峰值
    def find_peaks_above_min_height(self,x, size, min_height, max_num):
        
        i = 0
        n_peaks = 0
        ir_valley_locs = []  # [0 for i in range(max_num)]
        while i < size - 1:
            
            # 寻找上升的潜在峰值
            if x[i] > min_height and x[i] > x[i-1]:  
                n_width = 1
                
                # 判断平台的情况
                while i + n_width < size - 1 and x[i] == x[i+n_width]:  # find flat peaks
                    n_width += 1
                
                # x[i]>x[i-1] 且 x[i] > x[i+n_width] 说明发现峰值
                if x[i] > x[i+n_width] and n_peaks < max_num:  # find the right edge of peaks
                    ir_valley_locs.append(i)
                    n_peaks += 1  
                    i += n_width + 1
                else:
                    i += n_width
            else:
                i += 1

        return ir_valley_locs, n_peaks
        
    def remove_close_peaks(self,n_peaks, ir_valley_locs, x, min_dist):
        
        # 根据幅度值进行排序
        sorted_indices = sorted(ir_valley_locs, key=lambda i: x[i])
        sorted_indices.reverse()

        i = -1
        while i < n_peaks:
            old_n_peaks = n_peaks
            n_peaks = i + 1
            
            # 以i为基准 j逐渐增加
            # 判断i，j 之间的距离 小于min_dist，则去掉该点，后面的点依次前移动
            
            # i 从-1 开始，表示距离起始位置小于 min_dist 的点都清除
            j = i + 1
            while j < old_n_peaks:
                n_dist = (sorted_indices[j] - sorted_indices[i]) if i != -1 else (sorted_indices[j] + 1)  # lag-zero peak of autocorr is at index -1
                if n_dist > min_dist or n_dist < -1 * min_dist:
                    sorted_indices[n_peaks] = sorted_indices[j]
                    n_peaks += 1  
                j += 1
            i += 1

        sorted_indices[:n_peaks] = sorted(sorted_indices[:n_peaks])

        return sorted_indices, n_peaks 

    def run(self):
        while True:
            num_bytes = self.sensor.get_data_present()
            if num_bytes > 0:
                while num_bytes > 0:
                    red, ir = self.sensor.read_fifo()
                    num_bytes -= 1
                    self.ir_data.append(ir)
                    self.red_data.append(red)

                    while len(self.ir_data) > self.buffer_size:
                        self.ir_data.pop(0)
                        self.red_data.pop(0)

                    if len(self.ir_data) == self.buffer_size:
                        if (np.mean(self.ir_data) < 50000 and np.mean(self.red_data) < 50000):
                            self.flag_finger = False
                            self.hr_mean = 0
                            self.spo2_mean = 0
                            self.signals.hr_spo2_updated.emit(self.flag_finger, self.hr_mean, self.spo2_mean)
                        else:
                            self.flag_finger = True
                            hr_valid, hr, spo2_valid, spo2 = self.calc_hr_spo2(self.ir_data, self.red_data)
                            if hr_valid and spo2_valid:
                                self.bpms.append(hr)
                                self.spo2s.append(spo2)
                            while len(self.bpms)>4:
                                self.bpms.pop(0)
                                self.spo2s.pop(0)
                                self.hr_mean = np.mean(self.bpms)
                                self.spo2_mean = np.mean(self.spo2s)
                                self.signals.hr_spo2_updated.emit(self.flag_finger, self.hr_mean, self.spo2_mean)
            time.sleep(1)

#主class
class MyApp(QtWidgets.QMainWindow, Ui_mainWindow):
    #闪烁信号
    blink_signal = QtCore.pyqtSignal()
    #创建实例后的初始化
    def __init__(self) :
        super().__init__()
        self.setupUi(self)
        #dht11初始化
        self.dht11 = DHT11(pin_dic['G16DHT11'])
        self.last_dht_data = [0,0]
        #ds18b20初始化
        self.ds18b20 = DS18B20("28-00000065b9be")
        #定时更新dht11数据
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_sensor_data)
        self.timer.start(500)
        #pc链接
        self.host = '192.168.31.8'#pc地址
        self.port = 12345
        self.tcp_client = TCPClient(self.host, self.port)
        #线程处理发送数据
        send_thread = threading.Thread(target=self.send_data_thread)
        send_thread.start()
        #风扇按钮
        self.fan_on.clicked.connect(self.fan_on_clicked)
        self.fan_off.clicked.connect(self.fan_off_clicked)
        #制热按钮
        self.heat_on.clicked.connect(self.heat_on_clicked)
        self.heat_off.clicked.connect(self.heat_off_clicked)
        #应答按钮触发
        self.pushButton.clicked.connect(self.stop_blinking)
        #max30102初始化
        self.sensor_reader = Max30102()
        self.sensor_reader.signals.hr_spo2_updated.connect(self.update_hr_spo2)
        #创建一个线程来读取DS18B20数据
        self.ds18b20_thread = threading.Thread(target=self.read_ds18b20_data)
        self.ds18b20_thread.daemon = True  # 设置为守护线程，程序退出时自动关闭
        self.ds18b20_thread.start()
        #闪烁定时器
        self.blink_timer = QTimer(self)
        self.blink_timer.timeout.connect(self.blink)
        self.red_flag = False
        #连接信号和槽
        self.blink_signal.connect(self.start_blink_timer)
        #呼铃
        self.ring_pin = pin_dic['G26RING']
        GPIO.setup(self.ring_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(self.ring_pin, GPIO.FALLING, callback=self.start_blinking, bouncetime=200)


    #呼铃闪烁
    def start_blinking(self,channel):#(self,channel)
        self.blink_signal.emit()
    def start_blink_timer(self):
        self.blink_timer.start(500)
    #点击ui中的应答按钮触发的停止闪烁函数
    def stop_blinking(self):
        self.blink_timer.stop()
        self.patient_ring.setText("呼铃")
        self.patient_ring.setStyleSheet("background-color: rgb(255, 255, 255);")
    #闪烁函数
    def blink(self):
        self.patient_ring.setText("呼叫中")
        if self.red_flag:
            self.patient_ring.setStyleSheet("background-color: rgb(255, 255, 255);")
            self.red_flag = False
        else:
            self.patient_ring.setStyleSheet("background-color: rgb(255, 0, 0);")
            self.red_flag = True
    #max30102更新
    def start_sensor_reading(self):
        self.sensor_reader.start()
    def update_hr_spo2(self, flag_finger, hr_mean, spo2_mean):
        #print("Flag finger value:", flag_finger)
        if not flag_finger:
            self.patient_hb.setText("Waiting...")
            self.patient_SpO2.setText("Waiting...")
        else:
            self.patient_hb.setText("%.2f BPM"% hr_mean)
            self.patient_SpO2.setText("%.2f%%"% spo2_mean)
    # 更新DHT11传感器数据
    def update_sensor_data(self):

        flag, data = self.dht11.read_DHT()
        if flag:
            self.last_dht_data = data
        self.enTemp.setText(str(self.last_dht_data[0]) + "°C")
        self.enHumi.setText(str(self.last_dht_data[1]) + "%")
    #线程读取ds18b20数据
    def read_ds18b20_data(self):
        while True:
            # 读取DS18B20传感器数据
            t = self.ds18b20.read_DS18B20()     
            if t:
                # 更新UI界面上显示的DS18B20温度数据
                self.patient_Temp.setText("%2.1f °C"%(t))
            time.sleep(0.5)  # 读取间隔为0.5秒
    #获取label的值
    def get_label_data(self):
        label_data = {
            "enTemp": self.enTemp.text(),
            "enHumi": self.enHumi.text(),
            "patient_Temp": self.patient_Temp.text(),
            "patient_hb": self.patient_hb.text(),
            "patient_SpO2": self.patient_SpO2.text(),
            "ring_key": self.patient_ring.text()
        }
        #print(label_data)
        return label_data
    
    #接收命令并执行
    def receive_command(self):
        while True:
            try:
                command = self.tcp_client.recv_data()
                if command == "FAN_ON":
                    print("Control from PC")
                    self.fan_on_clicked()
                elif command == "FAN_OFF":
                    print("Control from PC")
                    self.fan_off_clicked()
                elif command == "HEAT_ON":
                    print("Control from PC")
                    self.heat_on_clicked()
                elif command == "HEAT_OFF":
                    print("Control from PC")
                    self.heat_off_clicked()
                elif command == "stop_blinking":
                    print("STOP_blinking_Control from PC")
                    self.stop_blinking()
            except Exception as e:
                print(f"Error: {e}")

    def send_data_thread(self):
        try:
            while True:
                label_data = self.get_label_data()
                #将标签数据转换为字符串，并发送到 PC
                data_str = ",".join([f"{key}:{value}" for key, value in label_data.items()])
                self.tcp_client.send_data(data_str)
                #每1s发送一次
                time.sleep(1)
        except Exception as e:
            print(f"Error: {e}")
    #风扇控制函数
    def fan_on_clicked(self):
        GPIO.output(pin_dic['G18FAN'], GPIO.LOW)        # 打开风扇
        if GPIO.input(pin_dic['G18FAN']) == GPIO.LOW:
             print('fan_ON')
    def fan_off_clicked(self):
        GPIO.output(pin_dic['G18FAN'], GPIO.HIGH)         # 关闭风扇
        if GPIO.input(pin_dic['G18FAN']) == GPIO.HIGH:
             print('fan_OFF')
    #制热控制函数
    def heat_on_clicked(self):
        GPIO.output(pin_dic['G12HEAT'],GPIO.LOW)
        if GPIO.input(pin_dic['G12HEAT']) == GPIO.LOW:
            print('heat_ON')
    def heat_off_clicked(self):
        GPIO.output(pin_dic['G12HEAT'],GPIO.HIGH)
        if GPIO.input(pin_dic['G12HEAT']) == GPIO.HIGH:
            print('heat_OFF')

        

    #清理GPIO引脚
    def cleangpio(self):
        GPIO.cleanup()

if __name__ == "__main__":
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(pin_dic['G18FAN'], GPIO.OUT)
    GPIO.setup(pin_dic['G12HEAT'], GPIO.OUT)
    GPIO.output(pin_dic['G18FAN'], GPIO.HIGH)
    GPIO.output(pin_dic['G12HEAT'], GPIO.HIGH)
    app = QtWidgets.QApplication(sys.argv) 
    window = MyApp()
    window.show()
    #心率血氧线程
    window.start_sensor_reading()
    #接收命令线程
    command_thread = threading.Thread(target=window.receive_command)
    command_thread.start()
    app.aboutToQuit.connect(window.cleangpio)
    sys.exit(app.exec_())
